﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Collections;
using System.Reflection;
using System.Diagnostics;

namespace Syk.Library.Extensions
{
    [DebuggerStepThrough]
    public static class ObjectExtensions
    {
        /// <summary>
        /// Write object to string
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static string DumpToString(this object obj)
        {
            return obj.DumpToString(0);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="depth">object graph with depth of more than 1</param>
        /// <returns></returns>
        public static string DumpToString(this object obj, int depth)
        {
            return ObjectDumper.DumpToString(obj, depth);
        }

        [DebuggerStepThrough]
        private class ObjectDumper
        {
            private TextWriter m_Writer;
            private int m_Pos;
            private int m_Level;
            private int m_Depth;

            public static string DumpToString(object obj)
            {
                return DumpToString(obj, 0);
            }

            public static string DumpToString(object obj, int depth)
            {
                StringBuilder sb = new StringBuilder();
                try
                {
                    using (StringWriter sw = new StringWriter(sb))
                    {
                        ObjectDumper dumper = new ObjectDumper(depth, sw);
                        dumper.WriteObject(null, obj);
                    }
                }
                catch
                {

                }

                return sb.ToString();
            }

            private ObjectDumper(int depth, TextWriter textWriter)
            {
                m_Writer = textWriter;
                m_Depth = depth;
            }

            private void Write(string str)
            {
                if (str != null)
                {
                    m_Writer.Write(str);
                    m_Pos += str.Length;
                }
            }

            private void WriteIndent()
            {
                for (int i = 0; i < m_Level; i++) m_Writer.Write("  ");
            }

            private void WriteLine()
            {
                m_Writer.WriteLine();
                m_Pos = 0;
            }

            private void WriteTab()
            {
                Write("  ");
                while (m_Pos % 8 != 0) Write(" ");
            }

            private void WriteObject(string prefix, object obj)
            {
                IEnumerable enumerable = obj as IEnumerable;

                if (obj == null || obj is ValueType || obj is string)
                {
                    WriteIndent();
                    Write(prefix);
                    WriteValue(obj);
                    WriteLine();
                }
                else if (enumerable != null)
                {
                    foreach (object element in enumerable)
                    {
                        if (element is IEnumerable && !(element is string))
                        {
                            WriteIndent();
                            Write(prefix);
                            Write("...");
                            WriteLine();
                            if (m_Level < m_Depth)
                            {
                                m_Level++;
                                WriteObject(prefix, element);
                                m_Level--;
                            }
                        }
                        else
                        {
                            WriteObject(prefix, element);
                        }
                    }
                }
                else
                {
                    MemberInfo[] members = obj.GetType().GetMembers(BindingFlags.Public | BindingFlags.Instance);
                    WriteIndent();
                    Write(prefix);
                    bool propWritten = false;
                    foreach (MemberInfo m in members)
                    {
                        FieldInfo f = m as FieldInfo;
                        PropertyInfo p = m as PropertyInfo;
                        if (f != null || p != null)
                        {
                            if (propWritten)
                            {
                                WriteTab();
                            }
                            else
                            {
                                propWritten = true;
                            }
                            Write(m.Name);
                            Write("=");
                            Type t = f != null ? f.FieldType : p.PropertyType;
                            if (t.IsValueType || t == typeof(string))
                            {
                                WriteValue(f != null ? f.GetValue(obj) : p.GetValue(obj, null));
                            }
                            else
                            {
                                if (typeof(IEnumerable).IsAssignableFrom(t))
                                {
                                    Write("...");
                                }
                                else
                                {
                                    Write("{ }");
                                }
                            }
                        }
                    }
                    if (propWritten) WriteLine();
                    if (m_Level < m_Depth)
                    {
                        foreach (MemberInfo m in members)
                        {
                            FieldInfo f = m as FieldInfo;
                            PropertyInfo p = m as PropertyInfo;
                            if (f != null || p != null)
                            {
                                Type t = f != null ? f.FieldType : p.PropertyType;
                                if (!(t.IsValueType || t == typeof(string)))
                                {
                                    object value = f != null ? f.GetValue(obj) : p.GetValue(obj, null);
                                    if (value != null)
                                    {
                                        m_Level++;
                                        WriteObject(m.Name + ": ", value);
                                        m_Level--;
                                    }
                                }
                            }
                        }
                    }
                }
            }

            private void WriteValue(object obj)
            {
                if (obj == null)
                {
                    Write("null");
                }
                else if (obj is DateTime)
                {
                    Write(((DateTime)obj).ToShortDateString());
                }
                else if (obj is ValueType || obj is string)
                {
                    Write(obj.ToString());
                }
                else if (obj is IEnumerable)
                {
                    Write("...");
                }
                else
                {
                    Write("{ }");
                }
            }
        }
    }
}
